package fr.cephb.dbsnptools;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;


import fr.cephb.bio.AcidNucleicUtils;
import fr.cephb.bio.FastaMapping;
import fr.cephb.io.IOUtils;
import fr.cephb.util.AbstractCmdLine;

/**
 * 
 * ExtractReference
 *
 */
public class ExtractReference
	extends AbstractCmdLine
	{
	private int mode=0;
	private FastaMapping mapFiles=new FastaMapping();
	private File base=null;
	private static class ChromMapping
		{
		int length=0;
		File tmpFile=null;
		RandomAccessFile io;
		}
	private Map<String, ChromMapping> chromMap=new HashMap<String, ChromMapping>();
	
	private void linearize(String name,ChromMapping m) throws IOException
		{
		if(m.tmpFile!=null) return;
		File sourceFile=this.mapFiles.get(name);
		if(sourceFile==null) throw new IOException("Not chromosome named "+name);
		FileInputStream fin=new FileInputStream(sourceFile);
		m.tmpFile= File.createTempFile("_tmp", ".fa",this.base);
		m.tmpFile.deleteOnExit();
		LOG.info("created file "+m.tmpFile.toString());
		int c=fin.read();
		if(c!='>') throw new IOException("not starting with '>' "+sourceFile);
		///skip fasta header
		while((c=fin.read())!=-1)
			{
			if(c=='\n') break;
			}
		m.io=new RandomAccessFile(m.tmpFile, "rw");
		byte array[]=new byte[8192];
		int nRead;
		while((nRead=fin.read(array))!=-1)
			{
			int j=0;
			for(int i=0;i< nRead;++i)
				{
				if( Character.isWhitespace(array[i])||
					Character.isDigit(array[i])) continue;
				array[j]=(byte)Character.toUpperCase(array[i]);
				j++;
				}
			m.io.write(array,0,j);
			m.length+=j;
			}
		fin.close();
		}
	

	@Override
	protected void usage(PrintStream out)
		{
		super.usage(out);
		out.println(" -base <basedir> (optional)");
		out.println(" -map <xml-mapping-fasta-file> (required)");
		out.println(" -c <file> add the fasta file");
		out.println(" -g <groupLabel> groupLabel: default:"+this.groupLabel);
		out.println(" -b <genomeBuild> groupLabel: default:"+this.genomeBuild);
		out.println("name chrom strand start end");
		}
	
	
	
	@Override
	protected int processArg(String[] args, int optind)
		{
		if(args[optind].equals("-base"))
			{
			this.base=new File(args[++optind]);
			return optind;
			}
		else if(args[optind].equals("-m"))
			{
			this.mode=Integer.parseInt(args[++optind]);
			return optind;
			}
		else if(args[optind].equals("-c"))
			{
			File f=new File(args[++optind]);
			this.mapFiles.put(f.getName(),f);
			return optind;
			}
		else if(args[optind].equals("-g"))
			{
			this.groupLabel=args[++optind];
			return optind;
			}
		else if(args[optind].equals("-b"))
			{
			this.genomeBuild=args[++optind];
			return optind;
			}
		else if(args[optind].equals("-map"))
			{
			try
				{
				this.mapFiles.read(new File(args[++optind]));
				for(File f:this.mapFiles.getFiles())
					{
					ChromMapping m=new ChromMapping();
					for(String name:this.mapFiles.getNames(f))
						{
						this.chromMap.put(name, m);
						}
					}
				}
			catch (Exception e)
				{
				throw new RuntimeException(e);
				}
			return optind;
			}
		return super.processArg(args, optind);
		}
	
	private String groupLabel="reference";
	private String genomeBuild="36_3";
	int flanking=60;
	
	private void rundbsnp(String input)  throws Exception
		{
		InputStream in=IOUtils.openInputStream(input);
		SnpHandler handler=new SnpHandler()
			{
			@Override
			public void found(SnpRecord rec)
				{
				try
					{
					String err="";
					int num_print=0;
					
					
					for(SnpRecord.MapLoc loc:rec.getLocations())
						{
						if(loc.groupLabel==null) continue;
						if(!loc.groupLabel.equals(ExtractReference.this.groupLabel)) continue;
						if(loc.genomeBuild==null) continue;
						if(!loc.genomeBuild.equals(ExtractReference.this.genomeBuild))
							{
							err+="No "+ExtractReference.this.genomeBuild+"\n";
							continue;
							}
						if(loc.leftContigNeighborPos==null)
							 {
							 err+="No leftContigNeighborPos\n";
							 continue;
							 }
						if(loc.rightContigNeighborPos==null)
							{
							err+="No rightContigNeighborPos\n";
							 continue;
							}
						if(loc.physMapInt==null)
							{
							err+="Not physMapInt\n";
							continue;
							}
						int len= (loc.rightContigNeighborPos-loc.leftContigNeighborPos)-1;
						int start=loc.physMapInt;
						int end=loc.physMapInt+len;
						if(len==0)
							{
							start++;
							end++;
							}
					
						
						ChromMapping c=ExtractReference.this.chromMap.get(loc.chromosome);
						if(c==null)
							{
							err+= "unknown chrom "+loc.chromosome+"\n";
							continue;
							}
						
						if(end<start)
							{
							err+= "end<start rs"+rec.getRsId();
							continue;
							}
						if(end>=c.length)
							{
							err+= "out of bound "+loc.chromosome+" in :rs"+rec.getRsId();
							continue;
							}
						System.out.print("rs"+rec.getRsId());
						System.out.print("\t");
						System.out.print(rec.getObserved());
						System.out.print("\t");
						System.out.print(loc.chromosome);
						System.out.print("\t");
						System.out.print(loc.orient);
						System.out.print("\t");
						System.out.print(start);
						System.out.print("\t");
						System.out.print(end);
						System.out.print("\t");
						
						StringBuilder sb=new StringBuilder();
						int pos=Math.max(start-flanking, 0);
						c.io.seek(pos);
						while(pos<start)
							{
							sb.append((char)c.io.read());
							++pos;
							}
						String left=sb.toString();
						
						sb.setLength(0);
						while(pos<end)
							{
							sb.append((char)c.io.read());
							++pos;
							}
						String mid=sb.toString();
						
						sb.setLength(0);
						while(pos< Math.min(end+flanking, c.length))
							{
							sb.append((char)c.io.read());
							++pos;
							}
						String right=sb.toString();
						
						if(loc.orient.equals("+") )
							{
							System.out.print(left);
							System.out.print("\t");
							System.out.print(mid);
							System.out.print("\t");
							System.out.print(right);
							}
						else
							{
							System.out.print(AcidNucleicUtils.reverseComplement(right));
							System.out.print("\t");
							System.out.print(AcidNucleicUtils.reverseComplement(mid));
							System.out.print("\t");
							System.out.print(AcidNucleicUtils.reverseComplement(left));
							}
						
						System.out.println();
						++num_print;
						}
				if(num_print==0)
					{
					//System.err.print("No position for rs"+rec.getRsId()+"\n"+err);
					}
				else if(num_print!=1)
					{
					//System.err.println("Multiples position ("+num_print+") for rs"+rec.getRsId());
					}
				}
			catch (Exception e) {
				e.printStackTrace();
				}
				}
			};
		try
			{
			handler.parse(new InputSource(in));
			}
		catch(SAXException err)
			{
			throw new IOException(err);
			}
		in.close();
		}
	
	
	private void run(BufferedReader r) throws Exception
		{
		Pattern delim=Pattern.compile("[\t]");
		String line;
		while((line=r.readLine())!=null)
			{
			String tokens[]=delim.split(line);
			if(tokens.length<5)
				{
				System.err.println("Expected 5 tokens in :"+line);
				continue;
				}
			String chrom=tokens[1];
			ChromMapping c=this.chromMap.get(chrom);
			if(c==null)
				{
				System.err.println("unknown chrom "+chrom+" in :"+line);
				continue;
				}
			String strand=tokens[2];
			int start= Integer.parseInt(tokens[3]);
			int end= Integer.parseInt(tokens[4]);
			if(end<=start)
				{
				System.out.println(line);
				continue;
				}
			if(end>=c.length)
				{
				System.err.println("out of bound "+chrom+" in :"+line);
				continue;
				}
			byte array[]=new byte[end-start];
			c.io.seek(start);
			c.io.readFully(array);
			String seq=new String(array);
			if(strand.equals("-"))
				{
				seq=AcidNucleicUtils.reverseComplement(seq);
				}
			
			System.out.print(line);
			System.out.print('\t');
			System.out.print(seq);
			System.out.println();
			}
		}
	
	
	
	
	public static void main(String[] args)
		{
		ExtractReference ref=null;
		try
			{
			ref=new ExtractReference();
			ref.base=new File(System.getProperty("user.dir"));
			int optind=ref.processArgs(args);
			
			if(ref.mapFiles.getFiles().isEmpty())
				{
				System.err.println("No file to be indexed");
				return;
				}
			
			LOG.info("processing chromosome");
			for(String name: ref.chromMap.keySet())
				{
				LOG.info("processing "+name);
				ref.linearize(name,ref.chromMap.get(name));
				}
			if(ref.mode==0)
				{
	       		if(optind==args.length)
	                    {
	                    LOG.info("reading from stdin");
	                    ref.run(new BufferedReader(new InputStreamReader(System.in)));
	                    }
	            else
	                    {
	                    while(optind< args.length)
		                        {
		                        LOG.info("reading  file "+args[optind]);
		                      	java.io.BufferedReader r= IOUtils.openReader(args[optind++]);
		                        ref.run(r);
		                        r.close();
		                        }
	                    }
				}
			else if(ref.mode==1)
				{
				if(optind==args.length)
                    {
                    System.err.println("No dbsnp.xml files");
                    }
				else
                    {
                    while(optind< args.length)
	                        {
	                        LOG.info("reading  file "+args[optind]);
	                      	ref.rundbsnp(args[optind++]);
	                        }
                    }
				}
			else
				{
				System.err.println("Illegal mode");
				}
			}
		catch (Exception e)
			{
			e.printStackTrace();
			}
		finally
			{
			if(ref!=null)
				{
				for(ChromMapping m:ref.chromMap.values())
	       			{
	       			if(m.io!=null) try { m.io.close(); } catch(IOException err) {}
	       			if(m.tmpFile!=null) m.tmpFile.delete();
	       			}
				}
			}
		}
	}
